package com.junxonline.instant.admin.service.config.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yitter.idgen.YitIdHelper;
import com.junxonline.instant.dao.entity.dto.ui.TreeNodeDTO;
import com.junxonline.instant.dao.entity.dto.ui.TreeSelectNodeDTO;
import com.junxonline.instant.dao.entity.model.config.DataSource;
import com.junxonline.instant.dao.entity.vo.config.DataSourceVO;
import com.junxonline.instant.dao.mapper.config.DataSourceMapper;
import com.junxonline.instant.admin.service.config.DataSourceService;
import com.junxonline.instant.common.constant.CommonConstant;
import com.junxonline.instant.common.entity.common.CommonReqPage;
import com.junxonline.instant.common.entity.common.CommonRespPage;
import com.junxonline.instant.common.enumeration.AdminErrorEnum;
import com.junxonline.instant.common.exception.AdminBizException;
import lombok.extern.log4j.Log4j2;
import org.apache.dubbo.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.*;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据源管理接口实现类
 *
 * @author JunX
 * @date 2021-08-02
 */
@Log4j2
@Service
@Transactional
public class DataSourceServiceImpl extends ServiceImpl<DataSourceMapper, DataSource> implements DataSourceService {

    @Value("${datasource.mysqlDumpBinPath}")
    private String mysqlDumpBinPath;

    @Value("${datasource.mysqlBinPath}")
    private String mysqlBinPath;

    @Value("${datasource.backupPath}")
    private String backupPath;

    @Value("${datasource.operationSystemType}")
    private String operationSystemType;

    @Value("${datasource.databaseName}")
    private String databaseName;

    @Value("${datasource.databaseHost}")
    private String databaseHost;

    @Value("${datasource.databasePort}")
    private String databasePort;

    @Value("${spring.datasource.dynamic.datasource.master.username}")
    private String databaseUsername;

    @Value("${spring.datasource.dynamic.datasource.master.password}")
    private String databasePassword;

    public final String ENCODING = "utf8";

    public final String SUFFIX = ".sql";

    @Resource
    public DruidDataSource druidDataSource;

    @Autowired
    private DataSourceMapper dataSourceMapper;

    /**
     * 查询备份文件列表
     *
     * @param vo    请求参数
     * @return CommonRespPage<dataSource>
     */
    @Override
    public CommonRespPage<DataSource> queryList(CommonReqPage<DataSourceVO> vo) {
        LambdaQueryWrapper<DataSource> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StrUtil.isNotBlank(vo.getQueryParams().getName()), DataSource::getName, vo.getQueryParams().getName());
        wrapper.like(StrUtil.isNotBlank(vo.getQueryParams().getUsername()), DataSource::getUsername, vo.getQueryParams().getUsername());
        if (vo.getQueryParams().getCreateTime() != null) {
            wrapper.between(DataSource::getCreateTime
                    ,vo.getQueryParams().getCreateTime()[0] + CommonConstant.QUERY_TIME_APPEND_START_TIME
                    ,vo.getQueryParams().getCreateTime()[1] + CommonConstant.QUERY_TIME_APPEND_END_TIME);
        }
        return CommonRespPage.success(dataSourceMapper.selectPage(new Page<>(vo.getPage(), vo.getPageSize()), wrapper));
    }

    /**
     * 查询备份表列表（Tree）
     *
     * @param id ID
     * @return List<TreeNodeDTO>
     */
    @Override
    public List<TreeNodeDTO> queryTableTree(Long id) {
        List<TreeNodeDTO> tables = new ArrayList<>();
        DataSource dataSource = dataSourceMapper.selectById(id);
        if (dataSource != null) {
            String[] tableNames = dataSource.getTables().split(",");
            Long key = 1L;
            Map<String, String> scopedSlots = new HashMap<>();
            scopedSlots.put("title", "title");
            for (String tableName : tableNames) {
                tables.add(new TreeNodeDTO(key, key, tableName, scopedSlots));
                key++;
            }
        }
        return tables;
    }

    /**
     * 查询数据库表列表（TreeSelect）
     *
     * @return List<TreeSelectNodeDTO>
     */
    @Override
    public List<TreeSelectNodeDTO> queryTableTreeSelect() {
        List<TreeSelectNodeDTO> tables = new ArrayList<>();
        // 获取当前数据库库表信息
        Connection connection = null;
        try {
            connection = druidDataSource.getConnection();
            DatabaseMetaData dbMetaData = connection.getMetaData();
            ResultSet rs = dbMetaData.getTables(null, "instant", null, new String[]{"TABLE"});
            Long id = 1L;
            Map<String, String> scopedSlots = new HashMap<>();
            scopedSlots.put("title", "title");
            while (rs.next()) {
                String tableName = rs.getString("TABLE_NAME");
                // 跳过不备份此表
                if (tableName.equals("sys_data_source")) {
                    continue;
                }
                tables.add(new TreeSelectNodeDTO(id, id, tableName, scopedSlots));
                id++;
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new AdminBizException(AdminErrorEnum.INTERNAL_SERVER_ERROR);
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                    connection = null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return tables;
    }

    /**
     * 备份数据库
     *
     * @param username 操作者用户名
     * @param tables   需要备份的表名称集合
     */
    @Override
    public void backup(String username, List<String> tables) {

        PrintWriter printWriter = null;
        BufferedReader bufferedReader = null;
        InputStreamReader inputStreamReader = null;
        OutputStreamWriter outputStreamWriter = null;
        FileOutputStream fileOutputStream = null;

        try {
            String tableNames = StringUtils.join(tables, ",");

            File file = new File(backupPath);
            if (!file.exists()) {
                file.mkdirs();
            }
            if (!backupPath.endsWith(File.separator)) {
                backupPath += File.separator;
            }

            String fileName = String.valueOf(YitIdHelper.nextId());
            fileOutputStream = new FileOutputStream(backupPath + fileName + SUFFIX);
            outputStreamWriter = new OutputStreamWriter(fileOutputStream, ENCODING);
            printWriter = new PrintWriter(outputStreamWriter);

            StringBuilder command = new StringBuilder();
            // Linux 需要去掉cmd /c
            if (!operationSystemType.equals("Linux")) {
                command.append("cmd /c ");
            }
            command.append(mysqlDumpBinPath);
            command.append(" -h");
            command.append(databaseHost);
            command.append(" -u");
            command.append(databaseUsername);
            command.append(" -p");
            command.append(databasePassword);
            command.append(" -P");
            command.append(databasePort);
            command.append(" --default-character-set=");
            command.append(ENCODING);
            command.append(" ");
            command.append(databaseName);

            for (String tableName : tables) {
                command.append(" ");
                command.append(tableName);
            }

            log.debug(">>>备份数据库命令：" + command.toString());

            Process process = Runtime.getRuntime().exec(command.toString());
            inputStreamReader = new InputStreamReader(process.getInputStream(), ENCODING);
            bufferedReader = new BufferedReader(inputStreamReader);
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                printWriter.println(line);
            }
            printWriter.flush();
            // 0 表示线程正常终止
            if (process.waitFor() == 0) {
                // 写入记录
                DataSource dataSource = new DataSource();
                dataSource.setName(fileName);
                dataSource.setSuffix(SUFFIX);
                dataSource.setUrl(backupPath);
                dataSource.setPath(backupPath);
                dataSource.setTables(tableNames);
                dataSource.setUsername(username);
                dataSourceMapper.insert(dataSource);
            }
        } catch (Exception e) {
            throw new AdminBizException(AdminErrorEnum.INTERNAL_SERVER_ERROR);
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }
                if (printWriter != null) {
                    printWriter.close();
                }
                if (outputStreamWriter != null) {
                    outputStreamWriter.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 还原数据库
     *
     * @param id ID
     */
    @Override
    public void restore(Long id) {
        try {
            File file = new File(backupPath);
            if (!file.exists()) {
                file.mkdirs();
            }
            if (!backupPath.endsWith(File.separator)) {
                backupPath += File.separator;
            }

            StringBuilder command = new StringBuilder();
            // Linux 需要去掉cmd /c
            if (!operationSystemType.equals("Linux")) {
                command.append("cmd /c ");
            }
            command.append(mysqlBinPath);
            command.append(" -h");
            command.append(databaseHost);
            command.append(" -u");
            command.append(databaseUsername);
            command.append(" -p");
            command.append(databasePassword);
            command.append(" -P");
            command.append(databasePort);
            command.append(" --default-character-set=");
            command.append(ENCODING);
            command.append(" ");
            command.append(databaseName);
            command.append("< ");
            DataSource dataSource = dataSourceMapper.selectById(id);
            command.append(dataSource.getPath());
            command.append(dataSource.getName());
            command.append(dataSource.getSuffix());

            log.debug(">>>还原数据库命令：" + command.toString());

            Process process = Runtime.getRuntime().exec(command.toString());
            // 0 表示线程正常终止
            if (process.waitFor() == 0) {
                // 还原成功
            }
        } catch (Exception e) {
            throw new AdminBizException(AdminErrorEnum.INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 删除备份文件
     *
     * @param ids 请求参数
     */
    @Override
    public void delete(List<Long> ids) {
        List<DataSource> dataSourceList = dataSourceMapper.selectBatchIds(ids);
        for (DataSource dataSource : dataSourceList) {
            // 删除物理文件
            File file = new File(dataSource.getPath() + dataSource.getName() + dataSource.getSuffix());
            if (file.exists()) {
                file.delete();
            }
            dataSourceMapper.deleteById(dataSource.getId());
        }
    }

}
